import { Component, cls, type StyledProps } from '@knno/jsx';
import { DICT } from '../../dict/dict';
import { Action } from '../action/action';
import { Button, ButtonPrimary } from '../button/button';
import { Dialog } from '../dialog/dialog';
import { Roller } from '../roller/roller';
import { isMobile } from '../selector/selector';
import { DATA } from '../symbol';
import { Tab } from '../tab/tab';
import { pickerStyle } from './picker.css';

export type PickValue = string | number;
export type PickUpdateOption = { keepPosition: boolean; gid: number; pid: number };
export type PickValueFunc = (selection: PickValue[][], option: PickUpdateOption) => PickValue[];
export type PickOptions = PickValue[] | PickValueFunc;
export interface PickGroup {
	name: string;
	parts: PickOptions[];
}

export type PickerProps = StyledProps<PickerBox> & {
	groups?: PickGroup[];
	options?: PickOptions[];
	value?: PickValue[][];
	onGroupActive?: (groupId: number) => void;
	onChange?: (value: PickValue[][]) => void;
};

export class PickerBox extends Component<PickerProps, PickerBox, HTMLDivElement> {
	private [DATA]: {
		groups: (PickGroup & { rollers: Roller[] })[];
		selection: PickValue[][];
		tab?: Tab;
	};

	protected render(): Node {
		return (
			<div class={cls(this.props.class, pickerStyle)} style={this.props.style}>
				<Tab
					ref={(t) => {
						this[DATA].tab = t;
					}}
					autoHideTabs
					onTabActive={(page) => {
						this.renderContent();
						this.props.onGroupActive?.(page);
					}}
				/>
			</div>
		);
	}

	constructor(props: PickerProps) {
		super(props);
		this[DATA] = { groups: [], selection: [] };
		void this.el;
		if (props.options) {
			this.setOptions(props.options ?? []);
		}
		if (props.groups) {
			this.setOptionGroups(props.groups ?? []);
		}
		this.setValue(props.value ?? []);
	}

	private renderContent() {
		const tb = this[DATA].tab;
		tb?.setTabs(this[DATA].groups.map((g) => g.name));
		tb?.box.replaceChildren(...this[DATA].groups.map((g, idx) => this.renderGroup(g, idx)));
	}

	private renderGroup(group: PickGroup, gid: number): Node {
		return (
			<div class="group">
				{group.parts.map((part, pid) => {
					let options: PickValue[];
					if (typeof part === 'function') {
						options = part(this.value, { keepPosition: false, gid, pid });
					} else {
						options = part;
					}
					const r = new Roller({
						options,
					});

					r.el.addEventListener('change', () => {
						// console.log('before roller changed:', this.value());
						if (this[DATA].selection[gid] === undefined) {
							this[DATA].selection[gid] = [];
						}
						this[DATA].selection[gid][pid] = r.value ?? options[0];
						if (pid + 1 < group.parts.length) {
							this.updateGroup(gid, pid + 1);
						}
						this.props.onChange?.(this.value);
					});
					this[DATA].groups[gid].rollers[pid] = r;
					let selectGroup = this[DATA].selection[gid];
					if (!selectGroup) {
						selectGroup = [];
						this[DATA].selection[gid] = selectGroup;
					}
					let v = selectGroup[pid];
					if (v) {
						r.setValue(v);
					}
					v = r.value ?? options[0];
					selectGroup[pid] = v;
					return r.el;
				})}
			</div>
		);
	}

	private updateGroup(gid: number, pid: number) {
		const group = this[DATA].groups[gid];
		for (let i = pid; i < group.parts.length; i++) {
			const part = group.parts[i];
			let options: PickValue[];
			const updateOption = { keepPosition: false, gid, pid };
			if (typeof part === 'function') {
				options = part(this.value, updateOption);
			} else {
				options = part;
			}
			const r = group.rollers[i];
			let pos = r.selected;
			r.setOptions(options);
			if (updateOption.keepPosition) {
				if (pos >= options.length) {
					pos = options.length - 1;
				}
				r.setSelected(pos);
			}
			this[DATA].selection[gid][i] = r.value ?? options[0];
		}
	}

	get activeGroup(): number {
		return this[DATA].tab?.active ?? 0;
	}
	setActiveGroup(index: number): this {
		this[DATA].tab?.setActive(index);
		return this;
	}

	get optionGroups(): PickGroup[] {
		return this[DATA].groups.map((g) => ({ name: g.name, parts: g.parts }));
	}
	setOptionGroups(groups: PickGroup[]): this {
		this[DATA].groups = groups.map((g) => ({ ...g, rollers: [] }));
		this[DATA].selection = groups.map(() => []);
		this.renderContent();
		return this;
	}

	setOptions(options: PickOptions[]): this {
		this[DATA].groups = [{ name: '', parts: options, rollers: [] }];
		this[DATA].selection = [[]];
		this.renderContent();
		return this;
	}

	get value(): PickValue[][] {
		return this[DATA].selection;
	}
	setValue(selection: PickValue[][]): this {
		this[DATA].selection = selection;
		this.renderContent();
		return this;
	}

	focus() {
		this.el.focus();
	}
}

export type ShowPickOptions = {
	title?: string;
	optionGroups?: PickGroup[];
	options?: PickOptions[];
	display?: 'action' | 'dialog' | 'auto';
	selection?: PickValue[][];
	minWidth?: string;
};

export const Picker = {
	show(options: ShowPickOptions): Promise<PickValue[][] | null> {
		return new Promise<PickValue[][] | null>((resolve) => {
			const pk = new PickerBox({ style: { border: 'none', minWidth: options.minWidth ?? '' } });
			if (options.optionGroups) {
				pk.setOptionGroups(options.optionGroups);
			} else if (options.options) {
				pk.setOptions(options.options);
			}
			if (options.selection) {
				pk.setValue(options.selection);
			}
			function showAction() {
				const a = Action.show(pk.el)
					.addClass('no-padding')
					.setTitle(options.title ?? '')
					.onOK(() => {
						a.close();
						resolve(pk.value);
					});
				a.onClose(() => {
					resolve(null);
				});
			}
			function showDialog() {
				const dlg = Dialog.create(pk.el).show().setAutoClose(true);
				if (options.title) {
					dlg.setTitle(options.title);
				}
				dlg
					.setButtons(
						<Button textContent={DICT.cancel} onClick={() => dlg.close()} />,
						<ButtonPrimary
							textContent={DICT.ok}
							onClick={() => {
								dlg.close();
								resolve(pk.value);
							}}
						/>,
					)
					.onClose(() => {
						resolve(null);
					});
			}
			if (options.display === 'dialog') {
				showDialog();
			} else if (options.display === 'action') {
				showAction();
			} else {
				if (isMobile()) {
					showAction();
				} else {
					showDialog();
				}
			}
		});
	},
};
